#include "GenericCurves.h"

using namespace cagd;
using namespace std;

GenericCurve::GenericCurve(GLuint point_count, GLuint maximum_order_of_derivative, GLenum usage_flag):
        _usage_flag(usage_flag),
        _vbo_derivative(RowMatrix<GLuint>(maximum_order_of_derivative + 1)),
        _derivative(Matrix<DCoordinate>(maximum_order_of_derivative + 1, point_count))
{
}

GenericCurve::GenericCurve(const GenericCurve &curve):
        _usage_flag(curve._usage_flag),
        _vbo_derivative(RowMatrix<GLuint>(curve._vbo_derivative.GetColumnCount())),
        _derivative(curve._derivative)
{
    GLboolean vbo_update_is_possible = true;
    for (GLuint i = 0; i < curve._vbo_derivative.GetColumnCount(); ++i)
        vbo_update_is_possible &= curve._vbo_derivative(i);

    if (vbo_update_is_possible)
        UpdateVertexBufferObjects(_usage_flag);
}

GenericCurve& GenericCurve::operator =(const GenericCurve &rhs)
{
    if (this != &rhs)
    {
        DeleteVertexBufferObjects();

        _usage_flag = rhs._usage_flag;
        _derivative = rhs._derivative;

        GLboolean vbo_update_is_possible = true;
        for (GLuint i = 0; i < rhs._vbo_derivative.GetColumnCount(); ++i)
            vbo_update_is_possible &= rhs._vbo_derivative(i);

        if (vbo_update_is_possible)
            UpdateVertexBufferObjects(_usage_flag);
    }
    return *this;
}

GLvoid GenericCurve::DeleteVertexBufferObjects()
{
    for (GLuint i = 0; i < _vbo_derivative.GetColumnCount(); ++i)
    {
        if (_vbo_derivative(i))
        {
            glDeleteBuffersARB(1, &_vbo_derivative(i));
            _vbo_derivative(i) = 0;
        }
    }
}

GLboolean GenericCurve::RenderDerivative(GLuint order, GLenum render_mode) const
{
    GLuint derivative_count = _derivative.GetRowCount();
    GLuint point_count = _derivative.GetColumnCount();

    if (order >= derivative_count)
        return false;

     if (!_vbo_derivative(order))
        return false;

    glEnableClientState(GL_VERTEX_ARRAY);
    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(order));
    glVertexPointer(3, GL_FLOAT, 0, (const GLvoid *)0);

    if (!order)
    {
        if (render_mode != GL_LINE_STRIP && render_mode != GL_LINE_LOOP && render_mode != GL_POINTS)
            return false;

        glDrawArrays(render_mode, 0, point_count);
    }
    else
    {
        if (render_mode != GL_LINES && render_mode != GL_POINTS)
            return false;

        glDrawArrays(render_mode, 0, 2 * point_count);
    }

    glDisableClientState(GL_VERTEX_ARRAY);
    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean GenericCurve::RenderCurvePoints(GLenum render_mode) const
{
    if (!_vbo_derivative(0))
        return false;

    if (render_mode != GL_LINE_STRIP && render_mode != GL_LINE_LOOP && render_mode != GL_POINTS)
        return false;

    glEnableClientState(GL_VERTEX_ARRAY);

        glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(0));
        glVertexPointer(3, GL_FLOAT, 0, (const GLvoid *)0);
        glDrawArrays(render_mode, 0, _derivative.GetColumnCount());

    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean GenericCurve::RenderTangentVectors(GLenum render_mode) const
{
    if (!_vbo_derivative(1))
        return false;

    if (render_mode != GL_LINES && render_mode != GL_POINTS)
        return false;

    glEnableClientState(GL_VERTEX_ARRAY);

        glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(1));
        glVertexPointer(3, GL_FLOAT, 0, (const GLvoid *)0);
        glDrawArrays(render_mode, 0, 2 * _derivative.GetColumnCount());

    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean GenericCurve::RenderAccelerationVectors(GLenum render_mode) const
{
    if (!_vbo_derivative(2))
        return false;

    if (render_mode != GL_LINES && render_mode != GL_POINTS)
        return false;

    glEnableClientState(GL_VERTEX_ARRAY);

        glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(2));
        glVertexPointer(3, GL_FLOAT, 0, (const GLvoid *)0);
        glDrawArrays(render_mode, 0, 2 * _derivative.GetColumnCount());

    glDisableClientState(GL_VERTEX_ARRAY);

    glBindBufferARB(GL_ARRAY_BUFFER, 0);

    return true;
}

GLboolean GenericCurve::UpdateVertexBufferObjects(GLenum usage_flag)
{
    if (!_derivative.GetRowCount())
        return false;

    if (!_derivative.GetColumnCount())
        return false;

    if (usage_flag != GL_STREAM_DRAW  && usage_flag != GL_STREAM_READ  && usage_flag != GL_STREAM_COPY &&
        usage_flag != GL_STATIC_DRAW  && usage_flag != GL_STATIC_READ  && usage_flag != GL_STATIC_COPY &&
        usage_flag != GL_DYNAMIC_DRAW && usage_flag != GL_DYNAMIC_READ && usage_flag != GL_DYNAMIC_COPY)
        return false;

    _usage_flag = usage_flag;

    DeleteVertexBufferObjects();

    for (GLuint d = 0; d < _vbo_derivative.GetColumnCount(); ++d)
    {
        glGenBuffersARB(1, &_vbo_derivative(d));
        if (!_vbo_derivative(d))
        {
            for (GLuint r = 0; r < d; ++r)
            {
                glDeleteBuffersARB(1, &_vbo_derivative(r));
                _vbo_derivative(r) = 0;
            }
            return false;
        }
    }

    GLuint point_count = _derivative.GetColumnCount();
    GLuint point_byte_size = point_count * 3 * sizeof(GLfloat);

    // auxiliar pointer for buffer data loading
    GLfloat* coordinate = 0;

    // curve points
    glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(0));
    glBufferDataARB(GL_ARRAY_BUFFER, point_byte_size, 0, _usage_flag);

    coordinate = (GLfloat*)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
    for (GLuint i = 0; i < point_count; ++i)
    {
        for (GLint j = 0; j < 3; ++j)
        {
            *coordinate = (GLfloat)_derivative(0, i)[j];
            ++coordinate;
        }
    }
    if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        return false;

    GLuint derivative_byte_size = 2 * point_byte_size;
    for (GLuint d = 1; d < _derivative.GetRowCount(); ++d)
    {
        // d-th order derivatives
        glBindBufferARB(GL_ARRAY_BUFFER, _vbo_derivative(d));
        glBufferDataARB(GL_ARRAY_BUFFER, derivative_byte_size, 0, _usage_flag);

        coordinate = (GLfloat *)glMapBufferARB(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
        for (GLuint i = 0; i < point_count; ++i)
        {
            DCoordinate sum = _derivative(0, i);
            sum += _derivative(d, i);

            for (GLint j = 0; j < 3; ++j)
            {
                *coordinate = (GLfloat)_derivative(0, i)[j];
                *(coordinate + 3) = (GLfloat)sum[j];
                ++coordinate;
            }
            coordinate += 3;
        }
        if (!glUnmapBufferARB(GL_ARRAY_BUFFER))
        {
            for (GLuint r = 0; r < d; ++r)
            {
                glDeleteBuffersARB(1, &_vbo_derivative(r));
                _vbo_derivative(r) = 0;
            }
            return false;
        }
    }

    return true;
}

GLboolean GenericCurve::SetDerivative(GLuint order, GLuint index, GLdouble x, GLdouble y, GLdouble z)
{
    if (order >= _derivative.GetRowCount())
        return false;

    if (index >= _derivative.GetColumnCount())
        return false;

    _derivative(order, index).x = x;
    _derivative(order, index).y = y;
    _derivative(order, index).z = z;

    return true;
}

GLboolean GenericCurve::SetDerivative(GLuint order, GLuint index, const DCoordinate& v)
{
    if (order >= _derivative.GetRowCount())
        return false;

    if (index >= _derivative.GetColumnCount())
        return false;

    _derivative(order, index) = v;

    return true;
}

GLboolean GenericCurve::GetDerivative(GLuint order, GLuint index, DCoordinate& result) const
{
    if (order >= _derivative.GetRowCount())
        return false;

    if (index >= _derivative.GetColumnCount())
        return false;

    result = _derivative(order, index);

    return true;
}

GLint GenericCurve::GetMaximumOrderOfDerivatives() const
{
    return (GLint)_derivative.GetRowCount() - 1;
}

GenericCurve::~GenericCurve()
{
    DeleteVertexBufferObjects();
}
